import { GUI } from 'dat.gui'
import {
  AxesHelper,
  BufferGeometry,
  DirectionalLight,
  EdgesGeometry,
  HemisphereLight,
  Light,
  LineBasicMaterial,
  LineSegments,
  LoadingManager,
  PerspectiveCamera,
  Scene,
  TextureLoader,
  WebGLRenderer,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Stats from 'three/examples/jsm/libs/stats.module'
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

class ThreeHelper {
  /** 最外层dom盒子 */
  wrapperDom: HTMLElement
  /** 渲染器 */
  renderer: WebGLRenderer
  /** 场景 */
  scene: Scene
  /** 摄像机 */
  camera: PerspectiveCamera
  lights: Light[] | []

  /** 加载管理器 */
  loadManager: LoadingManager
  /** gltf加载器 */
  gltfLoader: GLTFLoader | undefined
  /** 纹理加载器 */
  textureLoader: TextureLoader | undefined

  /** 辅助坐标轴 */
  axesHelper: AxesHelper | undefined
  /** 性能指示器 */
  stats: Stats | undefined
  /** 轨道控制器 */
  orbitControls: OrbitControls | undefined
  /** GUI控制器 */
  gui: GUI | undefined

  /** 是否播放动画，默认为true */
  runAnimate: boolean
  /**
   * requestAnimationFrame索引
   * 可用来销毁requestAnimationFrame
   */
  frameHandle: number | undefined

  private _animation: VoidFunction | undefined

  constructor(opt: { targetDom: HTMLElement; runAnimate?: boolean; listenResizeEvent?: boolean }) {
    const { targetDom, runAnimate = true, listenResizeEvent = true } = opt

    this.wrapperDom = targetDom
    this.wrapperDom.style.position = 'relative'

    // 初始化渲染器
    this.renderer = new WebGLRenderer({ antialias: true })
    this.renderer.setSize(this.wrapperDom.clientWidth, this.wrapperDom.clientHeight)
    this.wrapperDom.appendChild(this.renderer.domElement)

    this.scene = new Scene()

    // 初始化照相机
    this.camera = new PerspectiveCamera()
    this.camera.aspect = this.wrapperDom.clientWidth / this.wrapperDom.clientHeight
    this.camera.updateProjectionMatrix()
    this.camera.position.set(2, 10, 30)

    this.loadManager = new LoadingManager()

    this.lights = []

    this.runAnimate = runAnimate

    listenResizeEvent && window.addEventListener('resize', () => this._resizeEvent())
  }

  private _resizeEvent() {
    this.camera.aspect = this.wrapperDom.clientWidth / this.wrapperDom.clientHeight
    this.camera.updateProjectionMatrix()

    this.renderer.setSize(this.wrapperDom.clientWidth, this.wrapperDom.clientHeight)
    this.renderer.setPixelRatio(window.devicePixelRatio)
  }

  /**
   * 生成指定模型边缘
   */
  generateEdges<T extends BufferGeometry>(opt: { geometry: T; material?: LineBasicMaterial }) {
    const { geometry, material = new LineBasicMaterial({ color: 0xffffff }) } = opt

    const edges = new EdgesGeometry(geometry)
    const lineMaterial = material
    const line = new LineSegments(edges, lineMaterial)
    return line
  }

  /**
   * 加载GLTF模型
   */
  async loadGLTF(url: string) {
    if (!this.gltfLoader) {
      this.gltfLoader = new GLTFLoader(this.loadManager)
    }
    return this.gltfLoader!.loadAsync(url)
  }

  /**
   * 加载纹理
   */
  async loadTexture(url: string) {
    if (this.textureLoader) {
      this.textureLoader = new TextureLoader(this.loadManager)
    }
    return this.textureLoader?.loadAsync(url)
  }

  /**
   * 新增控制器
   */
  addOrbitControls(opt: { enableDamping: boolean } = { enableDamping: true }) {
    this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement)
    this.orbitControls.enableDamping = opt.enableDamping
  }

  /**
   * 镜头自动旋转
   */
  autoRotateCamera() {
    if (this.orbitControls) {
      this.orbitControls.autoRotate = true
      this.orbitControls.enableRotate = true
    }
  }

  /**
   * 添加辅助坐标轴
   */
  addAxesHelper(opt: { size: number } = { size: 15 }) {
    this.axesHelper = new AxesHelper(opt.size)
    this.scene.add(this.axesHelper)
  }

  /**
   * 添加性能指示器
   */
  addStats(
    opt: {
      position: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
      offset: [number, number]
    } = {
      position: 'top_left',
      offset: [0, 0],
    }
  ) {
    this.stats = new Stats()
    this.stats.dom.style.position = 'absolute'
    opt.position.split('_').forEach((key) => {
      const realKey = key as unknown as number
      this.stats!.dom.style[realKey] = `${opt.offset[0]}px`
      this.stats!.dom.style[realKey] = `${opt.offset[1]}px`
    })
    this.wrapperDom.appendChild(this.stats.dom)
  }

  addGUI(opt: { name: string } = { name: 'GUI_control' }) {
    this.gui = new GUI({ name: opt.name })
  }

  /**
   * 添加灯光
   */
  addCommonLights() {
    const hemisphereLight = new HemisphereLight(0xdddddd, 0xffffff, 1)
    hemisphereLight.position.set(0, -2, 110)
    const directionalLight = new DirectionalLight(0xffffff, 1)
    this.lights = [hemisphereLight, directionalLight]
    this.scene.add(...this.lights)
  }

  /**
   * 向环境中添加物体
   */
  add(...object: THREE.Object3D<THREE.Event>[]) {
    this.scene.add(...object)
  }

  /**
   * 渲染画布
   */
  render() {
    this.renderer.render(this.scene, this.camera)
  }

  /**
   * 设置每帧渲染执行的操作
   */
  animation(call: VoidFunction) {
    this._animation = call
  }

  /**
   * 逐帧渲染 frame(帧)
   */
  frameRender() {
    this.frameHandle = requestAnimationFrame(() => this.frameRender())
    this.orbitControls?.update()
    this.runAnimate && this._animation && this._animation()
    this.render()
    this.stats?.update()
  }

  /**
   *  停止逐帧渲染
   */
  stopFrameRender() {
    if (this.frameHandle) {
      cancelAnimationFrame(this.frameHandle)
    }
    this.frameHandle = 0
  }

  /**
   * 清理
   */
  destroy() {
    this.renderer.dispose()
    this.orbitControls?.dispose()
    this.gui?.destroy()
    window.removeEventListener('resize', this._resizeEvent)
  }
}

export default ThreeHelper
